package com.zhaorujie.rpc.core.remoting.invoker.proxy;

import com.zhaorujie.rpc.core.exception.RpcException;
import com.zhaorujie.rpc.core.register.Register;
import com.zhaorujie.rpc.core.remoting.invoker.RpcInvokerFactory;
import com.zhaorujie.rpc.core.remoting.netty.client.NettyClientConnect;
import com.zhaorujie.rpc.core.remoting.params.RpcFutureResponse;
import com.zhaorujie.rpc.core.remoting.params.RpcRequest;
import com.zhaorujie.rpc.core.remoting.params.RpcResponse;
import com.zhaorujie.rpc.core.remoting.provider.RpcProviderFactory;
import com.zhaorujie.rpc.core.route.LoadBalanceFactory;
import com.zhaorujie.rpc.core.route.RpcLoadBalance;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @author 赵汝杰
 * @version v1.0
 * @description JDK动态代理
 * @date 2024/3/13
 **/
public class ServiceProxy implements InvocationHandler {

    private final static Logger logger = LoggerFactory.getLogger(ServiceProxy.class);

    private final RpcInvokerFactory rpcInvokerFactory;
    private final String version;
    private final long timeout;
    private String loadBalance;

    public ServiceProxy(RpcInvokerFactory rpcInvokerFactory, String version, long timeout, String loadBalance) {
        this.rpcInvokerFactory = rpcInvokerFactory;
        this.version = version;
        this.timeout = timeout;
        this.loadBalance = loadBalance;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String className = method.getDeclaringClass().getName();
        String methodName = method.getName();
        Class<?>[] parameterTypes = method.getParameterTypes();
        if (className.equals(Object.class.getName())) {
            logger.info("rpc proxy class-method not support [{}#{}]", className, methodName);
            throw new RpcException("rpc proxy class-method not support");
        }
        Register register = rpcInvokerFactory.getRegister();
        if (register == null) {
            throw new RpcException("The registry was not initialized");
        }
        String finalAddress;
        String serviceKey = RpcProviderFactory.buildServiceKey(className, version);
        List<String> discovery = register.discovery(serviceKey);
        if (discovery == null || discovery.isEmpty()) {
            throw new RpcException("service not found");
        }
        // 如果只有一个实例注册到注册中心，直接返回当前实例的地址就行
        if (discovery.size() == 1) {
            finalAddress = discovery.get(0);
        } else {
            // 调度算法：1、随机 2、轮询等等
            if (this.loadBalance.isEmpty()){
                this.loadBalance = rpcInvokerFactory.getRpcProperties().getLoadBalance();
            }
            RpcLoadBalance loadBalance = LoadBalanceFactory.getInstance(this.loadBalance);
            // 获取对于的路由信息地址
            finalAddress = loadBalance.route(serviceKey, discovery);
        }

        // 创建RPC请求对象
        RpcRequest rpcRequest = new RpcRequest();
        rpcRequest.setRequestId(UUID.randomUUID().toString());
        rpcRequest.setVersion(version);
        rpcRequest.setClassName(className);
        rpcRequest.setParameters(args);
        rpcRequest.setMethodName(methodName);
        rpcRequest.setParameterTypes(parameterTypes);
        rpcRequest.setCreateTime(System.currentTimeMillis());

        // 发送请求
        RpcFutureResponse futureResponse = new RpcFutureResponse(rpcRequest, rpcInvokerFactory);
        try {
            new NettyClientConnect().send(rpcRequest, finalAddress, rpcInvokerFactory.getRpcSerialize(), rpcInvokerFactory);

            RpcResponse rpcResponse = futureResponse.get(timeout, TimeUnit.MILLISECONDS);
            if (rpcResponse.getThrowable() != null) {
                throw rpcResponse.getThrowable();
            }
            return rpcResponse.getResult();
        } catch (Exception e) {
            logger.error("rpc invoke error, address:{}, RpcRequest{}", finalAddress, rpcRequest);
            throw (e instanceof RpcException) ? e : new RpcException(e);
        } finally {
            futureResponse.removeInvokerFuture();
        }
    }
}
